package com.ks.zuul.gateway;



import com.ks.zuul.dao.RedisDao;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;

/**
 * @author kingdee
 * @date 2019/12/17 18:43
 */
@Component
@Slf4j
public class PreZuulFilter extends ZuulFilter {

    //允许不经过任何验证就能访问的接口地址清单
    private ArrayList<String> apiList = new ArrayList<String>(){{
        add("/*/swagger-ui.html");             //Swagger的界面
        add("/v2/api-docs");                   //Swaggerr的Api描述地址
        add("/*/v2/api-docs");
        add("/userBase/login");                //用户登录（租户）
        add("/sysUserBase/login");             //用户登录（系统）
        add("/orgTenant/tenantApplay");        //租户申请
        add("/base/upload");
        add("/base/uploadFile");
        add("/wx/saveTicket");
    }};

    @Autowired
    RedisDao redisDao;

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String url = request.getRequestURL().toString();
        HttpServletResponse response = ctx.getResponse();


        // 这些是对请求头的匹配，网上有很多解释
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH");
        response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, token");
        response.setHeader("Access-Control-Expose-Headers", "X-forwared-port, X-forwarded-host");
        response.setHeader("Vary", "Origin,Access-Control-Request-Method,Access-Control-Request-Headers");

        // 跨域请求一共会进行两次请求 先发送options 是否可以请求
        // 调试可发现一共拦截两次 第一次请求为options，第二次为正常的请求 eg：get请求
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
            ctx.setSendZuulResponse(false); //验证请求不进行路由
            ctx.setResponseStatusCode(HttpStatus.OK.value());//返回验证成功的状态码
            ctx.set("isSuccess", true);
            return null;
        }
        //如果访问的接口在公开接口清单中，就直接放行
        if (this.isOpenAPI(url)){
            return null;
        }else{
            String authorType = request.getHeader("AuthorType");
            if(authorType!=null && authorType.equalsIgnoreCase("Token")){
                if(!this.CheckAuthorToken(request.getHeader("AuthorToken"))){
                    this.SetRequestErro(ctx,401,"{\"code\":401,\"msg\":\"没有访问权限！\"}");
                }else{
                    if(!this.isTokenAlive(request.getHeader("AuthorToken"))){
                        this.SetRequestErro(ctx,401,"{\"code\":401,\"msg\":\"您的访问令牌失效，请重新登录！\"}");
                    }
                }
                return null;
            }
            if(authorType!=null && authorType.contentEquals("Secret")){
                if(!this.CheckSecret(request.getHeader("AppKey"),request.getHeader("AppAecret"))){
                    this.SetRequestErro(ctx,401,"{\"code\":401,\"msg\":\"没有访问权限！\"}");
                }
                return null;
            }

        }
        return null;
    }

    /**
     * 判断从Header中获取的AuthorToken是否过期
     * AuthonTokenr的结构：TenantID:OrgID:UserID:TimeStamp:Session
     * 在检验Token时同时对User的Token在Redis中的过期时间进行更新。默认重置为7天
     * @param authorToken
     * @return
     */
    private boolean isTokenAlive(String authorToken) {
        if(StringUtils.isBlank(authorToken)){
            return  false;
        }
        //用户请求时会在头部 Authorization 传给我之前存储的token, 我用来验证
        String[] split = authorToken.split(":");
        //查询redis是否有token
        String userID = (String) redisDao.getValue(split[2]);
        if (userID != null){
            redisDao.expired(userID,3600*24*7);
            return true;
        }
        return false;
    }

    /**
     * 向Http请求返回中加入请求错误响应
     * @param ctx
     * @param erroCode
     * @param body
     */
    private void SetRequestErro(RequestContext ctx,int erroCode,String body) {
        ctx.setSendZuulResponse(false);
        ctx.setResponseStatusCode(erroCode);
        ctx.setResponseBody(body);
        ctx.getResponse().setContentType("text/html;charset=UTF-8");
    }

    //验证调用接口的Secret方式的参数是否齐全
    private boolean CheckSecret(String appKey, String appSecret) {
        if(StringUtils.isBlank(appKey) || StringUtils.isBlank(appSecret)){
            return false;
        }
        return true;
    }
    //验证Token验证方式的参数是否正确
    private boolean CheckAuthorToken(String authorToken) {
       if(StringUtils.isBlank(authorToken)){
            return  false;
       }
       return true;
    }

    /**
     * 判断给定的URL是否在给定的开放接口清单中
     * 开放接口的意思是不需要进行Token或是Key的验证
     * @param url
     * @return
     */
    private boolean isOpenAPI(String url) {
        return this.apiList.contains(this.RemoveUrlRoot(url));
    }

    /**
     * 将一个URL的Root去掉
     * 可以把需要去掉的root配置在配置文件中
     * @param url
     * @return
     */
    private String RemoveUrlRoot(String url) {
        return url.replace("http://api.kingservice.com/","");
    }

}
